package edu.gatech.spacetraderteam3.view;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Manages the assets related to a planet to be painted on the screen
 * @author Alexander Ikonomidis
 * @version 1.0
 */
public abstract class PaintablePlanet implements PaintableBody {

    /**
     * The shadow effect on each planet
     */
    private final BufferedImage shadow = Assets.PlanetTexture.PLANET_SHADOW_MASK.image();

    /**
     * The texture on the planet
     */
    protected BufferedImage texture, buff, sBuff;

    /**
     * Runnables for the planet rotations
     */
    protected Runnable runnable, planetRender, shadowRender;

    /**
     * The thread pool for painting each planet
     */
    protected ExecutorService threadPool;

    /**
     * Array that holds the threads used
     */
    protected Thread[] renderThreads = new Thread[2];

    /**
     * The graphics buffer for painting
     */
    protected Graphics2D bufferGraphics;

    /**
     * Numerical values used for calculating rotations
     */
    protected double rotTheta, revTheta, offsetModifier, offsetY;

    /**
     * Constants from equation for an ellipse
     */
    protected double speed, ellipseA = 2, ellipseB = 0.5;

    /**
     * Double constants used for scaling the image
     */
    protected double x, y, scaleConstant = 1.0 / 1.5, scale = 1, rotSpeed = 1;

    /**
     * The offset Magnitude for each line of the image
     */
    private final double offsetMag = 1;

    /**
     * The width of the planet's texture
     */
    protected int textureWidth;

    /**
     * Constructor for the class. Sets texture, speed, other constants and sets up multithreaded stuff
     * @param planetTexture Texture of the Paintable Planet
     * @param speed Speed the planet moves
     */
    public PaintablePlanet(BufferedImage planetTexture, double speed) {
        this.texture = planetTexture;
        this.speed = speed;
        rotTheta = 0;
        revTheta = rotTheta;
        offsetY = Math.random() - 0.5 * offsetMag;
        offsetModifier = Math.abs(offsetY); // TODO: Make this data in the planet, so that it can be preserved accross sessions
        buff = new BufferedImage(texture.getHeight(), texture.getHeight(),
                BufferedImage.TYPE_INT_ARGB);

        textureWidth = texture.getWidth();
        runnable = new Runnable() {
            public void run() {
                move();
                rotate();
                prepareBuffer();
            }
        };
        planetRender = new Runnable() {
            public void run() {
                buff = ImageFilters
                        .seamlessSquareCrop(texture, rotTheta + offsetModifier);
                buff = ImageFilters.spherize(buff);
            }
        };
        shadowRender = new Runnable() {
            public void run() {
                sBuff = ImageFilters
                        .seamlessSquareCrop(shadow, rotTheta + offsetModifier);
                sBuff = ImageFilters.spherize(sBuff);
            }
        };
        threadPool = Executors.newFixedThreadPool(2);
    }

    /**
     * Getter for runnable element
     * @return The runnable element
     */
    public Runnable getRunnable() {
        return runnable;
    }

    /**
     * Paints planet
     * @param g Graphics component to paint on
     */
    public abstract void paint(Graphics2D g);

    /**
     * Rotates the planet
     */
    public void rotate() {
        rotTheta += rotSpeed;
    }

    /**
     * Prepares The Buffer  by getting the planet image
     */
    public void prepareBuffer() {
        planetRender.run();
        bufferGraphics = (Graphics2D) buff.getGraphics();
    }

    /**
     * Empty move method
     */
    public abstract void move();

    /**
     * Setter for revTheta
     * @param revTheta
     */
    public void setRevTheta(double revTheta) {
        this.revTheta = revTheta;
    }

    /**
     * Setter for rotTheta
     * @param rotTheta
     */
    public void setRotTheta(double rotTheta) {
        this.rotTheta = rotTheta;
    }

}
